﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Odbc;
using System.Linq;

namespace Database.ODBC
{
  /// <summary>
  /// ODBC query parameters.
  /// </summary>
  public class Parameters : IEnumerable<OdbcParameter>, IDisposable
  {
    private List<OdbcParameter> _parameters = new List<OdbcParameter>();

    /// <summary>
    /// Gets or sets the <see cref="OdbcParameter"/> at the specified index.
    /// </summary>
    /// <param name="index">The index.</param>
    /// <returns>OdbcParameter.</returns>
    /// <exception cref="System.IndexOutOfRangeException"></exception>
    public OdbcParameter this[int index]
    {
      get { return _parameters[index]; }
      set
      {
        if (index >= _parameters.Count)
        {
          throw new IndexOutOfRangeException();
        }

        _parameters[index] = value;
      }
    }

    /// <summary>
    /// Gets or sets the <see cref="OdbcParameter"/> with the specified name.
    /// </summary>
    /// <param name="parameterName">Parameter name.</param>
    /// <returns>OdbcParameter.</returns>
    /// <exception cref="KeyNotFoundException"></exception>
    public OdbcParameter this[string parameterName]
    {
      get
      {
        return _parameters.FirstOrDefault(t => t.ParameterName == parameterName);
      }
      set
      {
        var parameter = _parameters.FirstOrDefault(t => t.ParameterName == parameterName);

        if(parameter == null)
        {
          throw new KeyNotFoundException(string.Format("'{0}' does not exist in parameter collection", parameterName));
        }

        parameter.Value = value;
      }
    }

    /// <summary>
    /// Returns the number of parameters in the list.
    /// </summary>
    public int Count => _parameters.Count;

    /// <summary>
    /// Initializes a new instance of the <see cref="Parameters"/> class.
    /// </summary>
    public Parameters()
    { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Parameters"/> class.
    /// </summary>
    /// <param name="parameterName">Name of the parameter.</param>
    /// <param name="value">The value.</param>
    public Parameters(string parameterName, object value)
    {
      Add(parameterName, value);
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Parameters"/> class.
    /// </summary>
    /// <param name="parameterName">Name of the parameter.</param>
    /// <param name="value">The value.</param>
    /// <param name="direction">SQL <see cref="ParameterDirection"/></param>
    public Parameters(string parameterName, object value, ParameterDirection direction)
    {
      Add(parameterName, value, direction);
    }

    /// <summary>
    /// Finds a parameter matching the provided name.
    /// </summary>
    /// <param name="parameterName">Name of the parameter to find.</param>
    /// <returns>
    /// If found, returns the value of the index (positive integer). Otherwise, -1 is returned.
    /// </returns>
    [Obsolete("No longer required. Will be removed in v3. Please update your code base before updating to v3.")]
    public int FindParameter(string parameterName)
    {
      for (var index = 0; index < _parameters.Count; ++index)
      {
        if (_parameters[index].ParameterName == parameterName)
        {
          return index;
        }
      }

      return -1;
    }

    /// <summary>
    /// Adds the given parameter to the list of parameters
    /// </summary>
    /// <param name="parameter">Parameter to add</param>
    /// <returns>
    /// True if the parameter was added. False if a parameter with the same name already
    /// existed in the parameter list
    /// </returns>
    public bool Add(OdbcParameter parameter)
    {
      if(this[parameter.ParameterName] != null)
      {
        return false;
      }

      _parameters.Add(parameter);

      return true;
    }

    /// <summary>
    /// Creates a new parameter with the given name and value then adds it to 
    /// the list of parameters.
    /// </summary>
    /// <param name="parameterName">Name of the parameter</param>
    /// <param name="value">Value of the parameter</param>
    /// <returns>
    /// True if the parameter was added. False if a parameter with the same name already
    /// existed in the parameter list
    /// </returns>
    public bool Add(string parameterName, object value)
    {
      if (this[parameterName] != null)
      {
        return false;
      }

      _parameters.Add(new OdbcParameter(parameterName, value));

      return true;
    }

    /// <summary>
    /// Creates a new parameter with the given name and value then adds it to 
    /// the list of parameters.
    /// </summary>
    /// <param name="parameterName">Name of the parameter</param>
    /// <param name="value">Value of the parameter</param>
    /// <param name="direction">SQL <see cref="ParameterDirection"/></param>
    /// <returns>
    /// True if the parameter was added. False if a parameter with the same name already
    /// existed in the parameter list
    /// </returns>
    public bool Add(string parameterName, object value, ParameterDirection direction)
    {
      if(this[parameterName] != null)
      {
        return false;
      }

      var odbcParameter = new OdbcParameter(parameterName, value)
      {
        Direction = direction
      };


      _parameters.Add(odbcParameter);

      return true;
    }

    /// <summary>
    /// Creates a new parameter with the given name, database type, and value 
    /// then adds it to the list of parameters.
    /// </summary>
    /// <param name="parameterName">Name of the parameter</param>
    /// <param name="type">Type of the parameter</param>
    /// <param name="value">Value of the parameter</param>
    /// <returns>
    /// True if the parameter was added. False if a parameter with the same name already
    ///             existed in the parameter list
    /// </returns>
    public bool Add(string parameterName, OdbcType type, object value)
    {
      if(this[parameterName] != null)
      {
        return false;
      }

      var sqlParameter = new OdbcParameter(parameterName, value)
        {
          OdbcType = type
        };

      _parameters.Add(sqlParameter);

      return true;
    }

    /// <summary>
    /// Creates a new parameter with the given name, database type, and value 
    /// then adds it to the list of parameters.
    /// </summary>
    /// <param name="parameterName">Name of the parameter</param>
    /// <param name="type">Type of the parameter</param>
    /// <param name="value">Value of the parameter</param>
    /// <param name="direction">SQL <see cref="ParameterDirection"/></param>
    /// <returns>
    /// True if the parameter was added. False if a parameter with the same name already
    ///             existed in the parameter list
    /// </returns>
    public bool Add(string parameterName, OdbcType type, object value, ParameterDirection direction)
    {
      if(this[parameterName] != null)
      {
        return false;
      }

      var sqlParameter = new OdbcParameter(parameterName, value)
      {
        OdbcType = type,
        Direction = direction
      };

      _parameters.Add(sqlParameter);

      return true;
    }

    /// <summary>
    /// Removes a parameter with the provided name from the parameter list.
    /// </summary>
    /// <param name="parameterName">Name of the parameter to remove.</param>
    /// <returns>
    /// True if the parameter was found and removed. Otherwise, returns false.
    /// </returns>
    public bool Remove(string parameterName)
    {
      var parameter = this[parameterName];

      if (parameter == null)
      {
        return false;
      }

      _parameters.Remove(parameter);

      return true;
    }

    /// <summary>
    /// Removes the parameter at the provided index.
    /// </summary>
    /// <param name="index">Index of the parameter to remove</param>
    public void RemoveAt(int index)
    {
      _parameters.RemoveAt(index);
    }

    /// <summary>
    /// Clear all parameters from the parameter list.
    /// </summary>
    public void Clear()
    {
      _parameters.Clear();
    }

    /// <summary>
    /// Returns an enumerator that iterates through the collection.
    /// </summary>
    /// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
    public IEnumerator<OdbcParameter> GetEnumerator()
    {
      return _parameters.GetEnumerator();
    }

    /// <summary>
    /// Returns an enumerator that iterates through a collection.
    /// </summary>
    /// <returns>An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.</returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
      return _parameters.GetEnumerator();
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public void Dispose()
    {
      _parameters = null;
    }
  }
}